You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Synchronized network commands are given a unique id so that they can be sorted chronologically. The id needs to be unique per execution frame. It's a uint16 value so it'll overflow quite fast, especially because it's never reset and the starting value is 64000 by default.
When the network runahead decreases it's expected that multiple frames are executed right after each other. I noticed an issue with the overflow when I set the CRC interval to once per frame. When the overflow happened for a client combined with a decrease of the runahead, the order of the CRC messages was no longer guaranteed to be in chronological order for that execution frame. That would cause a mismatch if the overflow didn't happen for all clients at the same time.
I've added some test code in the first commit. If that's used without this fix, it becomes very apparent that the messages are not in the right order when the command id overflows. On top of that, it's very likely that the disconnect bug (disconnect screen with everyone at ~59XXX msec) occurs frequently.
I intend to do a follow up PR to make the static commandID variable a part of the NetCommandMsg class.
Caball009
added
Bug
Something is not working right, typically is user facing
Major
Severity: Minor < Major < Critical < Blocker
Network
Anything related to network, servers
Gen
Relates to Generals
ZH
Relates to Zero Hour
labels
May 19, 2026
This PR fixes a network desync caused by the uint16 command ID counter overflowing, which caused commands for the same execution frame to be sorted out of chronological order. The fix resets the counter to 0 every MAX_FRAMES_AHEAD (128) frames during active gameplay, ensuring the ID space never wraps within a reset cycle.
NetworkUtil.cpp: Promotes commandID from a function-local static to a file-scope static (starting at 0 instead of 64000) and adds ResetCommandID().
Network.cpp: Adds a m_lastFrameResetCommandID member and calls ResetCommandID() on init() and periodically in update() when frame >= m_lastFrameResetCommandID + MAX_FRAMES_AHEAD and the game is actively advancing frames (guarded by frame + m_runAhead > m_lastExecutionFrame).
NetCommandList.cpp: Documents why the sort logic intentionally has no overflow handling — the retail-compatible fix is resetting the counter rather than changing the comparator.
Confidence Score: 5/5
Safe to merge. The change is a well-scoped bugfix with clear logic and no regressions introduced in the normal game flow.
The reset fires at most once per game frame and every 128 frames during active play. Within a 128-frame window the uint16 counter cannot realistically wrap (even at 100 commands/frame that is ~12,800 IDs out of 65,535). The deduplication in isEqualCommandMsg is scoped to a single frame's NetCommandList, so cross-reset ID collisions cannot occur across frame boundaries. The starting value change from 64000 to 0 is intentional and correct.
Moves commandID from a function-local static to a file-level static and adds ResetCommandID(). First generated ID after reset is 1 (pre-increment). Starting value changed from 64000 → 0, intentional and correct with the new periodic reset.
Core/GameEngine/Include/GameNetwork/networkutil.h
Adds ResetCommandID() declaration. Header already uses #pragma once. No issues.
Core/GameEngine/Source/GameNetwork/Network.cpp
Adds m_lastFrameResetCommandID member, calls ResetCommandID() in init(), and adds a periodic reset every MAX_FRAMES_AHEAD (128) frames during active gameplay in update(). Also caches TheGameLogic->getFrame() into a local frame variable.
Adds an informational comment explaining why the insert-sort does not handle commandID overflow — the retail-compatible fix is to reset the counter rather than change the sort logic.
Sequence Diagram
sequenceDiagram
participant GL as GameLogic
participant N as Network::update()
participant NU as NetworkUtil (commandID)
participant NCL as NetCommandList::addMessage()
GL->>N: getFrame() → frame
Note over N: Check reset condition:<br/>frame + runAhead > lastExecutionFrame<br/>AND frame ≥ lastFrameResetCommandID + MAX_FRAMES_AHEAD
N->>NU: "ResetCommandID() → commandID = 0"
N->>N: GetCommandsFromCommandList()
N->>NU: "GenerateNextCommandID() → ++commandID (= 1, 2, …)"
NU-->>N: unique ID per command
N->>NCL: addMessage(cmd with ID)
Note over NCL: Insert-sort by type → playerID → commandID<br/>(no overflow check needed — ID reset prevents wrap)
N->>GL: RelayCommandsToCommandList(frame)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
BugSomething is not working right, typically is user facingGenRelates to GeneralsMajorSeverity: Minor < Major < Critical < BlockerNetworkAnything related to network, serversZHRelates to Zero Hour
1 participant
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Synchronized network commands are given a unique id so that they can be sorted chronologically. The id needs to be unique per execution frame. It's a uint16 value so it'll overflow quite fast, especially because it's never reset and the starting value is 64000 by default.
When the network runahead decreases it's expected that multiple frames are executed right after each other. I noticed an issue with the overflow when I set the CRC interval to once per frame. When the overflow happened for a client combined with a decrease of the runahead, the order of the CRC messages was no longer guaranteed to be in chronological order for that execution frame. That would cause a mismatch if the overflow didn't happen for all clients at the same time.
With a starting value of 64000 and a CRC message every frame the first overflow happens after ~25 seconds. You can see it mismatch here because of the overflow (this was with a special test build);
https://www.youtube.com/live/V_l-q9Y-DmA?t=621s
https://www.youtube.com/live/V_l-q9Y-DmA?t=9499s
I've added some test code in the first commit. If that's used without this fix, it becomes very apparent that the messages are not in the right order when the command id overflows. On top of that, it's very likely that the disconnect bug (disconnect screen with everyone at ~59XXX msec) occurs frequently.
I intend to do a follow up PR to make the
static commandIDvariable a part of theNetCommandMsgclass.